package jsr292.weaver.opt;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.List;

import jsr292.java.lang.invoke.Access;
import jsr292.java.lang.invoke.MethodHandle;
import jsr292.java.lang.invoke.MethodType;
import jsr292.weaver.Capabilities;
import jsr292.weaver.OnlineWeaver;
import jsr292.weaver.Utils;
import jsr292.weaver.Wrapper;
import jsr292.weaver.opt.OptimizerVarAllocator.Var;

import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;

public class OptimizerSnippets {
    public static void unreflectMethod(OptimizerContext context, OptimizerVarAllocator allocator, Method method, boolean isStatic) {
        if (!Access.isVisible(context.getClassBeingRedefined(), method)) {
            // method not visible, use reflection
            reflectCall(context, allocator, Type.getReturnType(method), (isStatic)? REFLECT_STATIC_METHOD_INVOKE: REFLECT_VIRTUAL_METHOD_INVOKE, method);
            return;
        }
        
        MethodVisitor mv = context.mv();
        for(Var var:allocator.getVarList()) {
            var.load(mv);
        }

        int opcode = (isStatic)? Opcodes.INVOKESTATIC: Opcodes.INVOKEVIRTUAL;
        mv.visitMethodInsn(opcode,
                Type.getInternalName(method.getDeclaringClass()),
                method.getName(),
                Type.getMethodDescriptor(method));
    }

    // entry point called from bytecode, see TrampolineGenerator
    public static void trampolineMethod(OptimizerContext context, OptimizerVarAllocator allocator, String declaringClassInternalName, String name, String descriptor, boolean isStatic) {
        MethodVisitor mv = context.mv();
        for(Var var:allocator.getVarList()) {
            var.load(mv);
        }

        int opcode = (isStatic)? Opcodes.INVOKESTATIC: Opcodes.INVOKEVIRTUAL;
        context.mv().visitMethodInsn(opcode, declaringClassInternalName, name, descriptor);
    }

    public static void unreflectConstructor(OptimizerContext context, OptimizerVarAllocator allocator, Constructor<?> constructor) {
        if (!Access.isVisible(context.getClassBeingRedefined(), constructor)) {
            // method not visible, use reflection
            reflectCall(context, allocator, Type.getType(constructor.getDeclaringClass()), REFLECT_CONSTRUCTOR_NEWINSTANCE, constructor);
            return;
        }

        String owner = Type.getInternalName(constructor.getDeclaringClass());
        MethodVisitor mv = context.mv();
        mv.visitTypeInsn(Opcodes.NEW, owner);
        mv.visitInsn(Opcodes.DUP);

        for(Var var:allocator.getVarList()) {
            var.load(mv);
        }
        mv.visitMethodInsn(Opcodes.INVOKESPECIAL, owner,
                "<init>", Type.getConstructorDescriptor(constructor));
    }

    public static void unreflectGetter(OptimizerContext context, OptimizerVarAllocator allocator, Field field) {
        if (!Access.isVisible(context.getClassBeingRedefined(), field)) {
            unsafeGetter(context, allocator, field);
            return;
        }
        
        MethodVisitor mv = context.mv();
        String owner = Type.getInternalName(field.getDeclaringClass());
        String fieldName = field.getName();
        String desc = Type.getType(field.getType()).getDescriptor();
        if (Modifier.isStatic(field.getModifiers())) {  
            mv.visitFieldInsn(Opcodes.GETSTATIC, owner, fieldName, desc);
        } else {
            allocator.getVar(0).load(mv);
            mv.visitFieldInsn(Opcodes.GETFIELD, owner, fieldName, desc);
        }
    }
    
    private static void unsafeGetter(OptimizerContext context, OptimizerVarAllocator allocator, Field field) {
        MethodVisitor mv = context.mv();
        if (Modifier.isStatic(field.getModifiers())) {
            mv.visitMethodInsn(Opcodes.INVOKESTATIC, "jsr292/weaver/Capabilities", "getUnsafe", "()Ljava/lang/Object;");
            mv.visitTypeInsn(Opcodes.CHECKCAST, "sun/misc/Unsafe");
            context.getBoundMap().loadAndBindStaticFieldBase(mv, field);
            mv.visitLdcInsn(Capabilities.staticFieldOffset(field));
        } else {
            Var var = allocator.getVar(0);
            
            // unsafe.get* doesn't check receiver for NPE
            var.load(mv);
            mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Object", "getClass", "()Ljava/lang/Class;");
            mv.visitInsn(Opcodes.POP);
            
            mv.visitMethodInsn(Opcodes.INVOKESTATIC, "jsr292/weaver/Capabilities", "getUnsafe", "()Ljava/lang/Object;");
            mv.visitTypeInsn(Opcodes.CHECKCAST, "sun/misc/Unsafe");
            var.load(mv);
            mv.visitLdcInsn(Capabilities.objectFieldOffset(field));
        }
        
        String getterName, getterDesc;
        Class<?> typeClass = field.getType();
        Type type = Type.getType(typeClass);
        int sort = type.getSort();
        switch(sort) {
        case Type.BOOLEAN:
            getterName = "getBoolean"; getterDesc = "(Ljava/lang/Object;J)Z";
            break;
        case Type.BYTE:
            getterName = "getByte"; getterDesc = "(Ljava/lang/Object;J)B";
            break;
        case Type.CHAR:
            getterName = "getChar"; getterDesc = "(Ljava/lang/Object;J)C";
            break;
        case Type.SHORT:
            getterName = "getShort"; getterDesc = "(Ljava/lang/Object;J)S";
            break;
        case Type.INT:
            getterName = "getInt"; getterDesc = "(Ljava/lang/Object;J)I";
            break;
        case Type.LONG:
            getterName = "getLong"; getterDesc = "(Ljava/lang/Object;J)J";
            break;
        case Type.FLOAT:
            getterName = "getFloat"; getterDesc = "(Ljava/lang/Object;J)F";
            break;
        case Type.DOUBLE:
            getterName = "getDouble"; getterDesc = "(Ljava/lang/Object;J)D";
            break;
        default:  // Type.OBJECT or Type.ARRAY
            getterName = "getObject"; getterDesc = "(Ljava/lang/Object;J)Ljava/lang/Object;";
        }
        
        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "sun/misc/Unsafe", getterName, getterDesc);
        
        if ((sort == Type.OBJECT || sort == Type.ARRAY) && typeClass != Object.class) {
            mv.visitTypeInsn(Opcodes.CHECKCAST, type.getInternalName());
        }
    }

    public static void unreflectSetter(OptimizerContext context, OptimizerVarAllocator allocator, Field field) {
        if (!Access.isVisible(context.getClassBeingRedefined(), field)) {
            unsafeSetter(context, allocator, field);
            return;
        }
        
        MethodVisitor mv = context.mv();
        String owner = Type.getInternalName(field.getDeclaringClass());
        String fieldName = field.getName();
        String desc = Type.getType(field.getType()).getDescriptor();
        if (Modifier.isStatic(field.getModifiers())) {  
            allocator.getVar(0).load(mv);
            mv.visitFieldInsn(Opcodes.PUTSTATIC, owner, fieldName, desc);
        } else {
            allocator.getVar(0).load(mv);
            allocator.getVar(1).load(mv);
            mv.visitFieldInsn(Opcodes.PUTFIELD, owner, fieldName, desc);
        }
    }
    
    private static void unsafeSetter(OptimizerContext context, OptimizerVarAllocator allocator, Field field) {
        MethodVisitor mv = context.mv();
        
        if (Modifier.isStatic(field.getModifiers())) {
            mv.visitMethodInsn(Opcodes.INVOKESTATIC, "jsr292/weaver/Capabilities", "getUnsafe", "()Ljava/lang/Object;");
            mv.visitTypeInsn(Opcodes.CHECKCAST, "sun/misc/Unsafe");
            context.getBoundMap().loadAndBindStaticFieldBase(mv, field);
            mv.visitLdcInsn(Capabilities.staticFieldOffset(field));
            allocator.getVar(0).load(mv);
        } else {
            Var var = allocator.getVar(0);
            
            // unsafe.put* doesn't check receiver for NPE
            var.load(mv);
            mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Object", "getClass", "()Ljava/lang/Class;");
            mv.visitInsn(Opcodes.POP);
            
            mv.visitMethodInsn(Opcodes.INVOKESTATIC, "jsr292/weaver/Capabilities", "getUnsafe", "()Ljava/lang/Object;");
            mv.visitTypeInsn(Opcodes.CHECKCAST, "sun/misc/Unsafe");
            var.load(mv);
            mv.visitLdcInsn(Capabilities.objectFieldOffset(field));
            allocator.getVar(1).load(mv);
        }
        
        String setterName, setterDesc;
        Class<?> typeClass = field.getType();
        Type type = Type.getType(typeClass);
        int sort = type.getSort();
        switch(sort) {
        case Type.BOOLEAN:
            setterName = "putBoolean"; setterDesc = "(Ljava/lang/Object;JZ)V";
            break;
        case Type.BYTE:
            setterName = "putByte"; setterDesc = "(Ljava/lang/Object;JB)V";
            break;
        case Type.CHAR:
            setterName = "putChar"; setterDesc = "(Ljava/lang/Object;JC)V";
            break;
        case Type.SHORT:
            setterName = "putShort"; setterDesc = "(Ljava/lang/Object;JS)V";
            break;
        case Type.INT:
            setterName = "putInt"; setterDesc = "(Ljava/lang/Object;JI)V";
            break;
        case Type.LONG:
            setterName = "putLong"; setterDesc = "(Ljava/lang/Object;JJ)V";
            break;
        case Type.FLOAT:
            setterName = "putFloat"; setterDesc = "(Ljava/lang/Object;JF)V";
            break;
        case Type.DOUBLE:
            setterName = "putDouble"; setterDesc = "(Ljava/lang/Object;JD)V";
            break;
        default:  // Type.OBJECT or Type.ARRAY
            setterName = "putObject"; setterDesc = "(Ljava/lang/Object;JLjava/lang/Object;)V";
        }
        
        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "sun/misc/Unsafe", setterName, setterDesc);
    }
    
    public static final int REFLECT_VIRTUAL_METHOD_INVOKE = 1;
    public static final int REFLECT_STATIC_METHOD_INVOKE = 2;
    public static final int REFLECT_CONSTRUCTOR_NEWINSTANCE = 3;
    
    public static void reflectCall(OptimizerContext context, OptimizerVarAllocator allocator, Type returnType, int reflectKind, Object reflectionObject) {
        MethodVisitor mv = context.mv();
        Class<?> reflectObjectClass = reflectionObject.getClass();
        context.getBoundMap().bindAndLoad(mv, reflectionObject, reflectObjectClass, reflectObjectClass);

        int size = allocator.getVarList().size();
        int startIndex = 0;
        int argumentCount = size;
        switch(reflectKind) {
        case REFLECT_VIRTUAL_METHOD_INVOKE:
            startIndex = 1;
            argumentCount--;
            allocator.getVar(0).load(mv);  // load receiver
            break;
        case REFLECT_STATIC_METHOD_INVOKE:
            mv.visitInsn(Opcodes.ACONST_NULL);
            break;
        case REFLECT_CONSTRUCTOR_NEWINSTANCE:
            // do nothing
            break;
        }
        
        // reflection allow to send null instead of an empty array
        if (argumentCount != 0) {
          Utils.ldcInteger(mv, argumentCount); 
          mv.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object");
        } else {
            mv.visitInsn(Opcodes.ACONST_NULL);
        }

        for (int index = startIndex; index < size; index++) {
            Var var = allocator.getVar(index);
            mv.visitInsn(Opcodes.DUP);
            Utils.ldcInteger(mv, index);
            
            var.load(mv);
            Wrapper wrapper = Wrapper.WRAPPERS[var.type.getSort()];
            if (wrapper != null) {
                mv.visitMethodInsn(Opcodes.INVOKESTATIC, wrapper.wrapperClassName, "valueOf", wrapper.boxingDesc);
            }

            mv.visitInsn(Opcodes.AASTORE);
        }

        Label tryStart = new Label();
        Label tryEnd = new Label();
        Label handler = new Label();
        mv.visitTryCatchBlock(tryStart, tryEnd, handler, "java/lang/reflect/InvocationTargetException");
        
        mv.visitLabel(tryStart);
        switch(reflectKind) {
        case REFLECT_VIRTUAL_METHOD_INVOKE:
        case REFLECT_STATIC_METHOD_INVOKE:
            mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/reflect/Method", "invoke", "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;");
            break;
        case REFLECT_CONSTRUCTOR_NEWINSTANCE:
            mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/reflect/Constructor", "newInstance", "([Ljava/lang/Object;)Ljava/lang/Object;");
            break;
        }
        mv.visitLabel(tryEnd);
        
        OnlineWeaver.generateGenericCallEpilog(mv, returnType);
        Label end = new Label();
        mv.visitJumpInsn(Opcodes.GOTO, end);
        mv.visitLabel(handler);
        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Throwable", "getCause", "()Ljava/lang/Throwable;");
        mv.visitInsn(Opcodes.ATHROW);
        
        mv.visitLabel(end);
    }

    public static void arrayGetter(OptimizerContext context, OptimizerVarAllocator allocator, Class<?> arrayClass) {
        MethodVisitor mv = context.mv();
        allocator.getVar(0).load(mv);
        allocator.getVar(1).load(mv);
        Type componentType = Type.getType(arrayClass.getComponentType());
        mv.visitInsn(componentType.getOpcode(Opcodes.IALOAD));
    }

    public static void arraySetter(OptimizerContext context, OptimizerVarAllocator allocator, Class<?> arrayClass) {
        MethodVisitor mv = context.mv();
        allocator.getVar(0).load(mv);
        allocator.getVar(1).load(mv);
        allocator.getVar(2).load(mv);
        Type componentType = Type.getType(arrayClass.getComponentType());
        mv.visitInsn(componentType.getOpcode(Opcodes.IASTORE));
    }

    public static void convert(OptimizerContext context, OptimizerVarAllocator allocator, MethodHandle target, MethodType newType) {
        MethodVisitor mv = context.mv();
        MethodType targetType = target.type();
        int lenght = targetType.parameterCount();
        for(int i=0; i<lenght; i++) {
            Converters.convertParameter(mv, allocator, i,
                    newType.parameterType(i), targetType.parameterType(i));
        }
        target.weave(context, allocator);
        Converters.convertOnStackReturnValue(mv, targetType.returnType(), newType.returnType());
    }

    public static void permute(OptimizerContext context, OptimizerVarAllocator allocator, MethodHandle target, int[] reorder) {
        MethodVisitor mv = context.mv();
        OptimizerVarAllocator newAllocator = allocator.createEmptyAllocator(allocator.slotCount);
        int length = reorder.length;
        for(int i=0; i<length; i++) {
            int order = reorder[i];
            Var var = allocator.getVar(order);

            if (order == i) {
                newAllocator.newVar(var.type, var.slotIndex);
                continue;
            }

            Var newVar = newAllocator.newVar(var.type);
            var.load(mv);
            newVar.store(mv);
        }

        target.weave(context, newAllocator);
    }

    public static void drop(OptimizerContext context, OptimizerVarAllocator allocator, MethodHandle target, int position, int length) {
        allocator.drop(position, length);
        target.weave(context, allocator);
    }

    
    // @return void.class means the type of null
    public static Class<?> getBindType(Class<?> callingClass, Class<?> declaredType, Object value) {
        if (value == null)
            return void.class;
        
        // can we use the real type ?
        Class<?> valueClass = value.getClass();
        if (Modifier.isPublic(valueClass.getModifiers()))
            return valueClass; 
        
        // can we use the declared type ?
        if (Modifier.isPublic(declaredType.getModifiers()))
            return declaredType; 
        
        // try to find a public super type that is compatible with the declared type
        Class<?> superType = Access.findPublicVisibleSuperType(valueClass, declaredType);
        if (superType != null)
            return superType;
        
        // can we cast to the declared type
        if (Access.isInSamePackage(callingClass, declaredType)) {
            return Access.findPublicVisibleSuperType(valueClass, null);
        }
        
        return null;
    }
    
    public static void constant(OptimizerContext context, Object value, Class<?> declaredType, Class<?> boundType) {
        context.getBoundMap().bindAndLoad(context.mv(), value, declaredType, boundType);
    }
    
    public static void insert(OptimizerContext context, OptimizerVarAllocator allocator, MethodHandle target, Object[] values, Class<?>[] boundTypes) {
        int length = values.length;

        Type[] types = new Type[length];
        for(int i=0; i<length; i++) {
            types[i] = Type.getType(target.type().parameterType(i));
        }

        allocator = allocator.insert(0, types);

        MethodVisitor mv = context.mv();
        for(int i=0; i<length; i++) {
            context.getBoundMap().bindAndLoad(mv, values[i], target.type().parameterType(i), boundTypes[i]);
            allocator.getVar(i).store(mv);
        }

        target.weave(context, allocator);
    }

    public static void spread(OptimizerContext context, OptimizerVarAllocator allocator, int arrayLength, MethodType newType, MethodHandle target) {
        List<Var> varList = allocator.getVarList();
        int lastPosition = varList.size() -1;

        OptimizerVarAllocator subAllocator = allocator.createEmptyAllocator(allocator.slotCount);
        for(int i=0; i<lastPosition; i++) {
            Var oldVar = allocator.getVar(i);
            subAllocator.newVar(oldVar.type, oldVar.slotIndex);
        }

        if (arrayLength != 0) {
            MethodVisitor mv = context.mv();
            
            // load array
            Var lastVar = allocator.getVar(lastPosition);
            lastVar.load(mv);

            MethodType targetType = target.type();
            Class<?> componentClass = newType.parameterType(lastPosition).getComponentType();
            
            for(int i=0; i<arrayLength; i++) {
                if (i != arrayLength - 1) {  // no dup for the last array cell
                    mv.visitInsn(Opcodes.DUP);
                }
                Utils.ldcInteger(mv, i);
                mv.visitInsn(Opcodes.AALOAD);

                Class<?> parameterType = targetType.parameterType(lastPosition + i);
                if (componentClass != parameterType) {
                    Converters.convertOnStackParameter(context.mv(), componentClass, parameterType);
                }

                Var var = subAllocator.newVar(Type.getType(parameterType)); 
                var.store(mv);
            }
        }
        target.weave(context, subAllocator);
    }

    public static void collect(OptimizerContext context, OptimizerVarAllocator allocator, int arrayLength, Class<?> componentClass, MethodType methodType, MethodHandle target) {
        MethodVisitor mv = context.mv();
        List<Var> varList = allocator.getVarList();
        int position = target.type().parameterCount() - 1;

        Utils.ldcInteger(mv, arrayLength);
        String componentName = Type.getInternalName(componentClass);
        mv.visitTypeInsn(Opcodes.ANEWARRAY, componentName);

        for(int i=0; i<arrayLength; i++) {
            mv.visitInsn(Opcodes.DUP);
            Utils.ldcInteger(mv, i);

            varList.get(position + i).load(mv);
            Class<?> parameterType = methodType.parameterType(position + i);
            if (parameterType != componentClass)
                Converters.convertOnStackParameter(mv, parameterType, componentClass);

            mv.visitInsn(Opcodes.AASTORE);
        }

        //FIXME, should not be necessary to create a new context
        OptimizerVarAllocator subAllocator = allocator.createEmptyAllocator(allocator.slotCount);
        for(int i=0; i<position; i++) {
            Var oldVar = varList.get(i);
            subAllocator.newVar(oldVar.type, oldVar.slotIndex);
        }

        Var lastVar = subAllocator.newVar(Type.getObjectType('[' + componentName));
        lastVar.store(mv);

        target.weave(context, subAllocator);
    }
    
    public static void varargsCollect(OptimizerContext context, OptimizerVarAllocator allocator,
            MethodType methodType, int targetLength, Class<?> componentType,
            MethodHandle target) {
        
        List<Var> varList = allocator.getVarList();
        
        // no need to collect anything
        if (varList.size() == targetLength) {
            target.weave(context, allocator);
            return;
        }
        
        int arrayLength = varList.size() - targetLength;
        MethodVisitor mv = context.mv();

        Utils.ldcInteger(mv, arrayLength);
        String componentName = Type.getInternalName(componentType);
        mv.visitTypeInsn(Opcodes.ANEWARRAY, componentName);

        for(int i=0; i<arrayLength; i++) {
            mv.visitInsn(Opcodes.DUP);
            Utils.ldcInteger(mv, i);

            varList.get(targetLength + i).load(mv);
            Class<?> parameterType = methodType.parameterType(targetLength + i);
            if (parameterType != componentType)
                Converters.convertOnStackParameter(mv, parameterType, componentType);

            mv.visitInsn(Opcodes.AASTORE);
        }

        Var lastVar = allocator.replaceVarType(targetLength - 1, Type.getObjectType('[' + componentName));
        lastVar.store(mv);
        
        target.weave(context, allocator);
    }

    public static void filter(OptimizerContext context, OptimizerVarAllocator allocator, MethodHandle target, MethodHandle[] filters) {
        MethodVisitor mv = context.mv();

        int length = filters.length;
        for(int i=0; i<length; i++) {
            MethodHandle filter = filters[i];
            if (filter == null)
                continue;

            Var var = allocator.getVar(i);
            MethodType filterType = filter.type();
            OptimizerVarAllocator localAllocator = new OptimizerVarAllocator(false,
                    0,
                    var.slotIndex,
                    Type.getType(filterType.parameterType(0)));
            filter.weave(context, localAllocator);

            var = allocator.replaceVarType(i, Type.getType(filterType.returnType()));
            var.store(mv);
        }

        target.weave(context, allocator);
    }
    
    public static void filterReturnValue(OptimizerContext context, OptimizerVarAllocator allocator, MethodHandle target, MethodHandle filter) {
        MethodVisitor mv = context.mv();
        OptimizerVarAllocator weaveAllocator = new OptimizerVarAllocator(allocator, allocator.allowReplaceInPlace);
        target.weave(context, weaveAllocator);
        
        Type type = Type.getType(target.type().returnType());
        OptimizerVarAllocator filterAllocator = allocator.createEmptyAllocator(allocator.slotCount);
        Var returnVar = filterAllocator.newVar(type);
        returnVar.store(mv);
        
        filter.weave(context, filterAllocator);
    }

    public static void throwz(OptimizerContext context, OptimizerVarAllocator allocator) {
        MethodVisitor mv = context.mv();
        allocator.getVar(0).load(mv);
        mv.visitInsn(Opcodes.ATHROW);
    }

    public static void identity(OptimizerContext context, OptimizerVarAllocator allocator) {
        allocator.getVar(0).load(context.mv());
    }

    public static void guard(OptimizerContext context, OptimizerVarAllocator allocator, MethodHandle test, MethodHandle target, MethodHandle fallback) {
        MethodVisitor mv = context.mv();

        test.weave(context, new OptimizerVarAllocator(allocator, false));
        Label elseLabel = new Label();
        mv.visitJumpInsn(Opcodes.IFEQ, elseLabel);

        target.weave(context, new OptimizerVarAllocator(allocator, true));
        Label endLabel = new Label();
        mv.visitJumpInsn(Opcodes.GOTO, endLabel);

        mv.visitLabel(elseLabel);
        fallback.weave(context, new OptimizerVarAllocator(allocator, true));

        mv.visitLabel(endLabel);
    }

    public static void fold(OptimizerContext context, OptimizerVarAllocator allocator, MethodHandle target, MethodHandle combiner) {
        int targetLength = target.type().parameterCount();
        int combinerLength = combiner.type().parameterCount();
        OptimizerVarAllocator combinerAllocator = new OptimizerVarAllocator(allocator, false);
        if (combinerLength != targetLength - 1) {
            //System.out.println("fold combinerLength: "+combinerLength+" targetLength: "+targetLength);
            combinerAllocator.drop(combinerLength, targetLength - combinerLength - 1);
        }
        combiner.weave(context, combinerAllocator);

        OptimizerVarAllocator targetAllocator = allocator.insert(0, Type.getType(target.type().parameterType(0)));
        targetAllocator.getVar(0).store(context.mv());
        target.weave(context, targetAllocator);
    }

    public static void catchz(OptimizerContext context, OptimizerVarAllocator allocator, MethodHandle target, MethodHandle handler, Class<? extends Throwable> exType) {
        MethodVisitor mv = context.mv();
        Type exceptionType = Type.getType(exType);
        OptimizerVarAllocator subAllocator = allocator.insert(0, exceptionType);

        allocator = new OptimizerVarAllocator(allocator, false);

        Label startTryLabel=new Label();
        Label endTryLabel=new Label();
        Label handlerLabel=new Label();
        mv.visitTryCatchBlock(startTryLabel, endTryLabel, handlerLabel, exceptionType.getInternalName());

        mv.visitLabel(startTryLabel);
        target.weave(context, allocator);
        mv.visitLabel(endTryLabel);
        Label endLabel = new Label();
        mv.visitJumpInsn(Opcodes.GOTO, endLabel);

        mv.visitLabel(handlerLabel);
        subAllocator.getVar(0).store(mv);
        handler.weave(context, subAllocator);

        mv.visitLabel(endLabel);
    }

    public static void switchPointGuard(OptimizerContext context, OptimizerVarAllocator allocator, boolean switchz, MethodHandle target, MethodHandle fallback) {
        if (switchz == false) {
            fallback.weave(context, allocator);
            return;
        }
        target.weave(context, allocator);
    }

    private static final Type JSR292_METHOD_HANDLE_TYPE = Type.getType(Utils.JSR292_METHOD_HANDLE_DESC);
    
    public static void exactInvoker(OptimizerContext context, OptimizerVarAllocator allocator, MethodType type, /*maybenull*/MethodHandle mh) {
        Type returnType = Type.getType(type.returnType());
        Class<?> classBeingRedefined = context.getClassBeingRedefined();
        OptimizerBoundMap boundMap = context.getBoundMap();
        if (mh != null && mh.isWeavable(classBeingRedefined)) {
            String mhFieldName = boundMap.bind(mh, JSR292_METHOD_HANDLE_TYPE);
            String ownerClassName = boundMap.getOwnerClassName();

            OptimizerWeaver.generateMethodHandleInvoke(context, allocator, ownerClassName, mhFieldName, returnType, mh);
        }
        
        // ask for deoptimization
        MethodVisitor mv = context.mv();
        mv.visitLdcInsn(Type.getType(classBeingRedefined));
        mv.visitMethodInsn(Opcodes.INVOKESTATIC, "jsr292/weaver/opt/Optimizer",
                "deoptimize", "(Ljava/lang/Class;)V");
        
        // generic call of method handle
        allocator.getVar(0).load(mv);
        OptimizerSnippets.genericCall(context, allocator, returnType, 1);
    }

    /**
     * This code is similar to OnlineWeaver.generateGenericCall and must be updated accordingly
     */
    static void genericCall(OptimizerContext context, OptimizerVarAllocator allocator, Type returnType, int startIndex) {
        MethodVisitor mv = context.mv();

        int size = allocator.getVarList().size();
        int argumentCount = size - startIndex;
        boolean arrayStore = argumentCount > Utils.PARAMETER_THRESHOLD;
        if (arrayStore) {
            Utils.ldcInteger(mv, argumentCount); // array length
            mv.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object");
        }

        for (int index = startIndex; index < size; index++) {
            Var var = allocator.getVar(index);

            if (arrayStore) {
                mv.visitInsn(Opcodes.DUP);
                Utils.ldcInteger(mv, index);
            }

            var.load(mv);
            Wrapper wrapper = Wrapper.WRAPPERS[var.type.getSort()];
            if (wrapper != null) {
                mv.visitMethodInsn(Opcodes.INVOKESTATIC, wrapper.wrapperClassName, "valueOf", wrapper.boxingDesc);
            }

            if (arrayStore) {
                mv.visitInsn(Opcodes.AASTORE);
            }
        }

        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Utils.JSR292_METHOD_HANDLE, "$invoke$", OnlineWeaver.invokeMethodDescriptor(argumentCount));

        OnlineWeaver.generateGenericCallEpilog(mv, returnType);
    }

    

    
}
